package concurrent.c_010_ThreadLocal;

import com.alibaba.ttl.TransmittableThreadLocal;
import com.alibaba.ttl.threadpool.TtlExecutors;
import lombok.Setter;
import lombok.ToString;
import lombok.experimental.Accessors;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

/**
 * https://blog.csdn.net/f641385712/article/details/104583169
 * <p>
 * 线程初始化方法
 *
 * @see Thread#Thread(ThreadGroup, Runnable, String, long, java.security.AccessControlContext, boolean)
 * * 当前线程作为新创建线程（子线程）的父线程
 * * 如果父线程绑定了变量（inheritableThreadLocals != null）并且允许继承（inheritThreadLocals = true），那么就会把父线程绑定的变量们拷贝一份到子线程里
 * * * 拷贝的原理类似于 Map 复制，只不过其在 Hash 冲突时，不是使用链表结构，而是直接在数组中找下一个为 null 的槽位放里面
 * * * 这里的拷贝是浅拷贝：引用传递而已。如果想要深度拷贝，需要自行复写 ThreadLocal#childValue()方法（比如你可以继承 InheritableThreadLocal 并重写 childValue 方法）
 * ThreadLocal 不具备继承性(子线程访问父线程中本地变量)，而 InheritableThreadLocal 可以（直接返回引用，创建子线程时将父线程中的本地变量值拷贝了一份到自己这来，拷贝的时机是子线程创建时）
 */
public class T02_ThreadLocalPool {
    // private static final ThreadLocal<Person> THREAD_LOCAL = new InheritableThreadLocal<>();
    // private static final ExecutorService THREAD_POOL = Executors.newSingleThreadExecutor();
    // private static final ExecutorService THREAD_POOL = Executors.newFixedThreadPool(2);

    // 实现类使用 TTL 的实现，线程池必须使用 TtlExecutors 处理一下，而且得使用 TransmittableThreadLocal 作为数据传递的实现
    private static final ThreadLocal<Person> THREAD_LOCAL = new TransmittableThreadLocal<>();
    // 线程池使用 TTL 包装一把，看线程名仍旧还是同一个线程（因为线程池大小为 1），但是结果已经是最新的了
    private static final ExecutorService THREAD_POOL = TtlExecutors.getTtlExecutorService(Executors.newSingleThreadExecutor());
    // 使用 TtlRunnable 包装了任务的运行，被包装的 run 方法执行异步任务之前，会使用 replay 进行设置，父线程里的本地变量给当前子线程，任务执行完毕，会调用 restore 恢复该子线程原生的本地变量

    public static void main(String[] args) throws InterruptedException {
        THREAD_LOCAL.set(new Person());
        // 使用 InheritableThreadLocal 来传递数据，那么线程池中的线程拷贝的数据始终来自于第一个提交任务的外部线程
        THREAD_POOL.execute(T02_ThreadLocalPool::getAndPrintData);
        TimeUnit.SECONDS.sleep(2);

        THREAD_LOCAL.set(new Person().setAge(100)); // 给线程重新绑定值，单线程池不生效
        THREAD_POOL.execute(() -> getAndPrintData());
        TimeUnit.SECONDS.sleep(2);

        THREAD_POOL.shutdown();
    }

    static void setData(Person person) {
        System.out.println("set 数据，线程名：" + Thread.currentThread().getName());
        THREAD_LOCAL.set(person);
    }

    static Person getAndPrintData() {
        Person person = THREAD_LOCAL.get();
        System.out.println("get 数据，线程名：" + Thread.currentThread().getName() + "，数据为：" + person);
        return person;
    }

    @Setter
    @ToString
    @Accessors(chain = true)
    private static class Person {
        private Integer age = 18;
    }
}